/*
 * Copyright (c) 2007, Tim Stack
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Tim Stack nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TIM STACK ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL TIM STACK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>

#include "reflecxx.h"

#ifndef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif

static const char *skipspace(struct reflecxx_buffer *rb)
{
    assert(rb != NULL);
    
    while (rb->rb_data[rb->rb_offset] != '\0' &&
	   rb->rb_offset < rb->rb_used &&
	   isspace(rb->rb_data[rb->rb_offset])) {
	rb->rb_offset += 1;
    }

    return &rb->rb_data[rb->rb_offset];
}

static void error_location(struct reflecxx_buffer *rb,
			   struct reflecxx_error *re)
{
    int lpc;

    re->re_line = 1;
    re->re_column = 0;
    for (lpc = 0;
	 lpc < rb->rb_offset && lpc < rb->rb_used && rb->rb_data[lpc] != '\0';
	 lpc++) {
	switch (rb->rb_data[lpc]) {
	case '\n':
	    re->re_line += 1;
	    re->re_column = 0;
	    break;
	case '\r':
	    re->re_column = 0;
	    break;
	default:
	    re->re_column += 1;
	    break;
	}
    }
}

int reflecxx_scan(struct reflecxx_buffer *rb,
		  struct reflecxx_full_type *rft,
		  reflecxx_primitive_t *rp_inout,
		  struct reflecxx_error *re)
{
    const char *ostr, *str, *sval, *expecting = "";
    int ival, cons = 0, retval = 0;
    unsigned int uival;
    
    assert(rb != NULL);
    assert(rb->rb_data != NULL);
    assert(rb->rb_used > 0);
    assert(rp_inout != NULL);
    assert(re != NULL);

    re->re_code = REC_OK;

    ostr = str = &rb->rb_data[rb->rb_offset];
    switch (rft->rft_type) {
    case RT_CHAR:
	expecting = "an 8-bit number";
	retval = sscanf(str, "%d%n", &ival, &cons);
	rp_inout->rp_char = ival;
	break;
    case RT_SHORT:
	expecting = "a 16-bit number";
	retval = sscanf(str, "%hd%n", &rp_inout->rp_short, &cons);
	break;
    case RT_INT:
	expecting = "a 32-bit number";
	retval = sscanf(str, "%d%n", &rp_inout->rp_int, &cons);
	break;
    case RT_LONG:
	expecting = "a 32-bit number";
	retval = sscanf(str, "%ld%n", &rp_inout->rp_long, &cons);
	break;
    case RT_LONG_LONG:
	expecting = "a 64-bit number";
	retval = sscanf(str, "%qd%n", &rp_inout->rp_long_long, &cons);
	break;
    case RT_UNSIGNED_CHAR:
	expecting = "an unsigned 8-bit number";
	retval = sscanf(str, "%u%n", &uival, &cons);
	rp_inout->rp_unsigned_char = uival;
	break;
    case RT_UNSIGNED_SHORT:
	expecting = "an unsigned 16-bit number";
	retval = sscanf(str, "%hu%n", &rp_inout->rp_unsigned_short, &cons);
	break;
    case RT_UNSIGNED_INT:
	expecting = "an unsigned 32-bit number";
	retval = sscanf(str, "%u%n", &rp_inout->rp_unsigned_int, &cons);
	break;
    case RT_UNSIGNED_LONG:
	expecting = "an unsigned 32-bit number";
	retval = sscanf(str, "%lu%n", &rp_inout->rp_unsigned_long, &cons);
	break;
    case RT_UNSIGNED_LONG_LONG:
	expecting = "an unsigned 64-bit number";
	retval = sscanf(str, "%qu%n", &rp_inout->rp_unsigned_long_long, &cons);
	break;
    case RT_FLOAT:
	expecting = "a floating point number";
	retval = sscanf(str, "%f%n", &rp_inout->rp_float, &cons);
	break;
    case RT_DOUBLE:
	expecting = "a floating point number";
	retval = sscanf(str, "%lf%n", &rp_inout->rp_double, &cons);
	break;
    case RT_POINTER:
	retval = 0;
	break;
    case RT_VOID:
	retval = 1;
	break;
    case RT_STRING:
	str = skipspace(rb);
	if ((rp_inout->rp_string = reflecxx_unescape(str, &cons)) != NULL) {
	    retval = 1;
	}
	else {
	    expecting = "a string";
	}
	break;
    case RT_ARRAY:
	if (str[0] != '[') {
	    re->re_code = REC_PARSE_ERROR;
	    snprintf(re->re_msg, sizeof(re->re_msg),
		     "expecting array definition");
	}
	else {
	    struct reflecxx_array *ra = rft->rft_type_info.arr;
	    unsigned int index = 0, len = ra->ra_hi - ra->ra_lo + 1;
	    size_t esize;

	    esize = reflecxx_sizeof(&ra->ra_element_type);
	    if (rp_inout->rp_pointer == NULL) {
		rp_inout->rp_pointer = reflecxx_malloc(len * esize);
		if (rp_inout->rp_pointer == NULL) {
		    re->re_code = REC_OUT_OF_MEMORY;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "out of memory");
		    error_location(rb, re);
		    return 0;
		}
		memset(rp_inout->rp_pointer, 0, len * esize);
	    }

	    rb->rb_offset += 1;
	    str = skipspace(rb);
	    while (str[0] != ']' && str[0] != '\0') {
		reflecxx_primitive_t rp;

		if (index >= len) {
		    re->re_code = REC_OUT_OF_RANGE;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "too many array elements");
		    error_location(rb, re);
		    return 0;
		}

		rp = reflecxx_array_get(rp_inout->rp_pointer, ra, index);
		if (!reflecxx_scan(rb, &ra->ra_element_type, &rp, re))
		    return 0;

		switch (ra->ra_element_type.rft_type) {
		case RT_STRUCT:
		case RT_UNION:
		case RT_ARRAY:
		    break;
		default:
		    reflecxx_array_set(rp_inout->rp_pointer, ra, index, rp);
		    break;
		}
		index += 1;
		
		str = skipspace(rb);
		if (str[0] != ',' && str[0] != ']') {
		    re->re_code = REC_PARSE_ERROR;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "expecting ',' or ']' in array definition");
		    error_location(rb, re);
		    return 0;
		}
		
		if (str[0] == ',') {
		    rb->rb_offset += 1;
		    str = skipspace(rb);
		}
	    }
	    if (str[0] != ']') {
		return 0;
	    }
	    rb->rb_offset += 1;
	    retval = 1;
	}
	break;
    case RT_ENUM:
	if (str[0] == '"' && (sval = strchr(str + 1, '"')) != NULL) {
	    struct reflecxx_enum_element *ree;
	    
	    ree = reflecxx_enum_by_name(rft, &str[1], (sval - str) - 1);
	    if (ree != &UNKNOWN_ENUM_ELEMENT) {
		rp_inout->rp_enum = ree->ree_value;
		retval = 1;
	    }
	    cons = (sval - ostr) + 1;
	}
	break;
    case RT_STRUCT:
	if (str[0] != '{') {
	    re->re_code = REC_PARSE_ERROR;
	    snprintf(re->re_msg, sizeof(re->re_msg),
		     "expecting object definition");
	}
	else {
	    struct reflecxx_struct *rs = rft->rft_type_info.st;
	    
	    if (rp_inout->rp_pointer == NULL) {
		rp_inout->rp_pointer = reflecxx_malloc(rs->rs_byte_length);
		if (rp_inout->rp_pointer == NULL)
		    return 0;
		memset(rp_inout->rp_pointer, 0, rs->rs_byte_length);
	    }

	    rb->rb_offset += 1;
	    str = skipspace(rb);
	    while (str[0] != '}' && str[0] != '\0') {
		struct reflecxx_field *rf;
		reflecxx_primitive_t rp;
		const char *term;
		int nlen;
		
		// printf("back %s %d\n", str, rb->rb_offset);
		str = skipspace(rb);
		if (str[0] != '"' || (term = strchr(str + 1, '"')) == NULL) {
		    re->re_code = REC_PARSE_ERROR;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "expecting string for object field name");
		    error_location(rb, re);
		    return 0;
		}
		str += 1;
		nlen = term - str;
		//  printf("st %s\n", str);
		if ((rf = reflecxx_field_by_name(rft, str, nlen)) == NULL) {
		    char name[32];

		    nlen = MIN(sizeof(name), nlen);
		    strncpy(name, str, nlen);
		    name[nlen] = '\0';
		    
		    re->re_code = REC_UNKNOWN_FIELD;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "unknown object field '%s'", name);
		    error_location(rb, re);
		    return 0;
		}
		rb->rb_offset += 1 + nlen + 1;
		str = skipspace(rb);
		if (str[0] != ':') {
		    re->re_code = REC_PARSE_ERROR;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "expecting ':' after object field name");
		    error_location(rb, re);
		    return 0;
		}
		rb->rb_offset += 1;
		// printf("ne %s\n", str);
		if (reflecxx_scan(rb, &rf->rf_full_type, &rp, re) == 0)
		    return 0;
		reflecxx_field_set(rp_inout->rp_pointer, rf, rp);
		str = skipspace(rb);
		// printf("re %s\n", str);
		if (str[0] != ',' && str[0] != '}') {
		    re->re_code = REC_PARSE_ERROR;
		    snprintf(re->re_msg, sizeof(re->re_msg),
			     "expecting ',' or '}' in object definition");
		    error_location(rb, re);
		    return 0;
		}
		if (str[0] == ',') {
		    rb->rb_offset += 1;
		    str = skipspace(rb);
		}
	    }
	    // printf("en %s\n", str);
	    if (str[0] != '}') {
		return 0;
	    }
	    rb->rb_offset += 1;
	    retval = 1;
	}
	break;
    }

    rb->rb_offset += cons;

    if (retval == 0) {
	if (re->re_code == REC_OK) {
	    re->re_code = REC_PARSE_ERROR;
	    snprintf(re->re_msg, sizeof(re->re_msg),
		     "parse error, expecting %s",
		     expecting);
	}

	error_location(rb, re);
    }
    
    return retval;
}
